package com.linkallcloud.web.utils;

import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.net.URLDecoder;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.alibaba.fastjson2.util.ParameterizedTypeImpl;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Controller;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.linkallcloud.core.domain.Domain;
import com.linkallcloud.core.domain.IDomain;
import com.linkallcloud.core.face.message.request.FaceRequest;
import com.linkallcloud.core.lang.Mirror;
import com.linkallcloud.core.lang.Strings;
import com.linkallcloud.core.log.Log;
import com.linkallcloud.core.log.Logs;
import com.linkallcloud.core.principal.AccountMapping;
import com.linkallcloud.core.principal.Assertion;
import com.linkallcloud.core.principal.Principal;
import com.linkallcloud.core.service.IService;
import com.linkallcloud.core.util.HibernateValidator;
import com.linkallcloud.core.www.ISimpleUser;
import com.linkallcloud.core.www.ITokenBox;
import com.linkallcloud.core.www.utils.UrlEncoderUtils;

public class Controllers {
    private static final Log log = Logs.get();

    public static final String TOKEN_PARAM_NAME = "lacticket";

    public static <U extends ISimpleUser> U getAppSessionUser(String appCode, HttpServletRequest request,
            ITokenBox tokenBox, Class<U> uclass) {
        if (tokenBox != null && uclass != null) {
            String token = Controllers.getLacToken(request);
            if (!Strings.isBlank(token)) {
                U suser = tokenBox.get(token, uclass);
                if (suser != null && suser.appCode().equals(appCode)) {
                    return suser;
                }
            }
        }
        return null;
    }

    public static <U extends ISimpleUser> U getSessionUser(HttpServletRequest request, ITokenBox tokenBox,
            Class<U> uclass) {
        if (tokenBox != null && uclass != null) {
            String token = Controllers.getLacToken(request);
            if (!Strings.isBlank(token)) {
                return tokenBox.get(token, uclass);
            }
        }
        return null;
    }

    public static String getLacToken(FaceRequest fr, HttpServletRequest request) {
        String token = fr.getToken();
        if (Strings.isBlank(token) || "undefined".equals(token) || "null".equals(token)) {
            token = Controllers.getLacToken(request);
        } else {
            if (UrlEncoderUtils.hasUrlEncoded(token)) {
                try {
                    token = URLDecoder.decode(token, "UTF8");
                } catch (UnsupportedEncodingException e1) {
                }
            }
        }
        return token;
    }

    public static String getLacToken(HttpServletRequest request) {
        String token = getLacTokenFromCookie(request);

        if (Strings.isBlank(token) || "undefined".equals(token) || "null".equals(token)) {
            token = getLacTokenFromHeader(request);
        }

        if (Strings.isBlank(token) || "undefined".equals(token) || "null".equals(token)) {
            token = getLacTokenFromParameter(request);
        }

        if (!Strings.isBlank(token) && !"undefined".equals(token) && !"null".equals(token)) {
            return token;
        } else {
            return null;
        }
    }

    private static String getLacTokenFromParameter(HttpServletRequest request) {
        String token = request.getParameter(Controllers.TOKEN_PARAM_NAME);
        if (!Strings.isBlank(token) && !"undefined".equals(token) && !"null".equals(token)) {
            if (UrlEncoderUtils.hasUrlEncoded(token)) {
                try {
                    token = URLDecoder.decode(token, "UTF8");
                } catch (UnsupportedEncodingException e1) {
                }
            }
        }
        return token;
    }

    private static String getLacTokenFromHeader(HttpServletRequest request) {
        String token = request.getHeader(Controllers.TOKEN_PARAM_NAME);
        if (!Strings.isBlank(token) && !"undefined".equals(token) && !"null".equals(token)) {
            if (UrlEncoderUtils.hasUrlEncoded(token)) {
                try {
                    token = URLDecoder.decode(token, "UTF8");
                } catch (UnsupportedEncodingException e1) {
                }
            }
        }
        return token;
    }

    private static String getLacTokenFromCookie(HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (int i = 0; i < cookies.length; i++) {
                if (cookies[i].getName().equals(Controllers.TOKEN_PARAM_NAME)) {
                    String token = cookies[i].getValue();
                    if (!Strings.isBlank(token) && !"undefined".equals(token) && !"null".equals(token)) {
                        if (UrlEncoderUtils.hasUrlEncoded(token)) {
                            try {
                                token = URLDecoder.decode(token, "UTF8");
                            } catch (UnsupportedEncodingException e1) {
                            }
                        }
                        return token;
                    } else {
                        continue;
                    }
                }
            }
        }
        return null;
    }

    public static void sendLacTokenCookie(String token, HttpServletRequest request, HttpServletResponse response) {
        Controllers.sendCookie(Controllers.TOKEN_PARAM_NAME, token, "/", request, response);
    }

    public static void removeLacTokenCookie(HttpServletRequest request, HttpServletResponse response) {
        Controllers.removeCookie(Controllers.TOKEN_PARAM_NAME, "/", request, response);
    }

    public static String getCookie(String key, HttpServletRequest request) {
        Cookie[] cookies = request.getCookies();
        if (cookies != null) {
            for (int i = 0; i < cookies.length; i++) {
                if (cookies[i].getName().equals(key)) {
                    String value = cookies[i].getValue();
                    if (!Strings.isBlank(value) && !"undefined".equals(value) && !"null".equals(value)) {
                        return value;
                    } else {
                        continue;
                    }
                }
            }
        }
        return null;

    }

    public static void sendCookie(String key, String value, String path, HttpServletRequest request,
            HttpServletResponse response) {
        Cookie gc = new Cookie(key, value);
        if (request.isSecure()) {
            gc.setSecure(true);
        }
        gc.setMaxAge(-1);
        gc.setPath(Strings.isBlank(path) ? request.getContextPath() : path);
        gc.setHttpOnly(true);
        response.addCookie(gc);
    }

    public static void removeCookie(String key, String path, HttpServletRequest request, HttpServletResponse response) {
        Cookie gc = new Cookie(key, null);
        if (request.isSecure()) {
            gc.setSecure(true);
        }
        gc.setMaxAge(0);
        gc.setPath(Strings.isBlank(path) ? request.getContextPath() : path);
        response.addCookie(gc);
    }

    /**
     * 得到SSO认证通过后设置的Assertion。
     *
     * @param request
     * @return Assertion
     */
    public static Assertion getAssertion(String appCode, HttpServletRequest request) {
        return (Assertion) request.getAttribute(appCode + Assertion.ASSERTION_KEY);
    }

    /**
     * 得到SSO认证通过后设置的Principal。
     *
     * @param request
     * @return Principal
     */
    public static Principal getSSOPrincipal(String appCode, HttpServletRequest request) {
        Assertion as = Controllers.getAssertion(appCode, request);
        if (as != null) {
            return as.getPrincipal();
        }
        return null;
    }

    public static String getLoginNameByPrincipal(Principal ssoPrincipal) {
        return ssoPrincipal.getMappingType() == AccountMapping.Mapping.getCode()
                && !Strings.isBlank(ssoPrincipal.getSiteId()) ? ssoPrincipal.getSiteId() : ssoPrincipal.getId();
    }

    public static String redirect(String format, Object... arguments) {
        return new StringBuffer("redirect:").append(MessageFormat.format(format, arguments)).toString();
    }

    public static <T> void beanValidator(T object) {
        HibernateValidator.getInstance().validatorThrowException(object);
    }

    public static <T extends Domain> Map<Long, T> getIdMap(List<T> entitys) {
        return entitys.stream().collect(Collectors.toMap(T::getId, Function.identity(), (key1, key2) -> key2));
    }

    public static String getRequestScheme(HttpServletRequest request) {
        String scheme = request.getHeader("X-Forwarded-Proto");
        if (Strings.isBlank(scheme)) {
            scheme = request.getHeader("X-Http-Scheme");
        }
        if (Strings.isBlank(scheme)) {
            scheme = request.getScheme();
        }
        return scheme;
    }

    public static String getRequestServerName(HttpServletRequest request) {
        String host = request.getHeader("X-Real-Host");
        if (Strings.isBlank(host)) {
            host = request.getServerName();
        }
        return host;
    }

    public static boolean checkReferer(HttpServletRequest request, boolean needReferer) {
        if (request == null) {
            request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        }
        if (request != null) {
            String referer = request.getHeader("referer");
            if (needReferer && Strings.isBlank(referer)) {
                log.warnf("此方法（%s）需要Referer检查，但是其值未空。", request.getRequestURI());
                return false;
            }

            StringBuffer sb = new StringBuffer();
            String scheme = Controllers.getRequestScheme(request);
            sb.append(scheme).append("://").append(Controllers.getRequestServerName(request));
            if (referer != null && !referer.equals("")) {
                String server = String.valueOf(sb);
                // if (referer.lastIndexOf(server) != 0) {
                if (!referer.startsWith(server)) {
                    log.warnf("Referer（%s）检查失败，Server（%s）。", referer, server);
                    return false; // 验证失败
                }
            }
        } else {
            log.warn("request is null,放弃本次Referer检查。");
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    public static Class<? extends IDomain> getDomainTClass(ProceedingJoinPoint joinPoint) {
        Class<?> clzz = joinPoint.getTarget().getClass();
        Mirror<?> cmirror = Mirror.me(clzz);
        Class<? extends IDomain> domainClass = null;
        if (cmirror.isOf(IService.class)) {// service
            IService<? extends IDomain> service = (IService<? extends IDomain>) joinPoint.getTarget();
            domainClass = service.getDomainClass();
        } else if (clzz.getAnnotation(Controller.class) != null) {// Controller
            Method method = null;
            try {
                method = clzz.getMethod("getDomainClass");
            } catch (NoSuchMethodException e) {
            } catch (SecurityException e) {
            }
            if (method != null) {
                try {
                    domainClass = (Class<? extends IDomain>) method.invoke(joinPoint.getTarget());
                } catch (IllegalAccessException e) {
                } catch (IllegalArgumentException e) {
                } catch (InvocationTargetException e) {
                }
            }
        }
        return domainClass;
    }

    /**
     * 
     * @param joinPoint
     * @param argType
     * @return
     */
    public static Type parseConcreteArgType(ProceedingJoinPoint joinPoint, Type argType) {
        if (argType instanceof ParameterizedType) {
            ParameterizedType ptype = (ParameterizedType) argType;
            if (ptype.getActualTypeArguments() != null && ptype.getActualTypeArguments().length > 0) {
                Type atype = ptype.getActualTypeArguments()[0];
                if (atype instanceof TypeVariable) {
                    TypeVariable<?> tvi = (TypeVariable<?>) atype;
                    if (tvi.getBounds() != null && tvi.getBounds().length > 0) {
                        boolean isDomain = tvi.getBounds()[0].getTypeName().equals(Domain.class.getName());
                        if (!isDomain) {
                            try {
                                Class<?> clazz = Class.forName(tvi.getBounds()[0].getTypeName());
                                isDomain = Domain.class.isAssignableFrom(clazz);
                            } catch (ClassNotFoundException e) {
                            }
                        }
                        if (isDomain) {
                            Class<? extends IDomain> clazz = Controllers.getDomainTClass(joinPoint);
                            Type[] actualTypeArguments = new Type[] { clazz };
                            argType = new ParameterizedTypeImpl(actualTypeArguments, ptype.getOwnerType(),
                                    ptype.getRawType());
                        }
                    }
                }
            }
        }
        return argType;
    }

    /**
     * 获取当前执行的方法
     *
     * @param joinPoint
     *            连接点
     * @return 方法
     */
    public static Method getMethod(JoinPoint joinPoint) {
        Signature sig = joinPoint.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = joinPoint.getTarget();
        try {
            return target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        } catch (NoSuchMethodException e) {
            log.error(e.getMessage(), e);
        } catch (SecurityException e) {
            log.error(e.getMessage(), e);
        }
        return null;
    }

}
